#include <stdio.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <grp.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <error.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <dirent.h>
#include <pwd.h>
#include <shadow.h>
#include <errno.h>
#include <pwd.h>

#include "err_msg.h"
#include "myftpserver.h"
#include "commands.h"
#include "utilities.h"
#include "common.h"


int send_msg(int fd, int num, ...);
char *crypt(const char *, const char *);
// foward declaration.
int close_all_fds();
ssize_t writen(int fd, const void *ptr, size_t n);
void trimstring(char *str, int len);

void handle_alarm(int signum)
{
	if(timeout_type) { // control timeout. kick the user.
		// kick user.	
		close_all_fds();
		exit(0);
	} 
	else { // data connection timeout.
		// close data connection.	
		close_all_fds();
		exit(0);
	}
}

int daemon_init(const char *pname, int facility) 
{
	int i;
	pid_t pid;

	if ((pid = fork()) < 0)
		return (-1);
	else if (pid) 
		_exit(0);

	if (setsid() < 0)
		return (-1);
	
	signal(SIGHUP, SIG_IGN);
	
	if ((pid = fork()) < 0) 
		return (-1);
	else if (pid)
		_exit(0);

	if (chdir("/")) {
		err_quit("chdir");
	}

	for (i = 0; i< MAXFD; i++)
		close(i);

	open("/dev/null", O_RDONLY);
	open("/dev/null", O_RDWR);
	open("/dev/null", O_RDWR);

	openlog(pname, LOG_PID, facility);

	return (0);
}

int close_all_fds()
{
	if (pasv_fd > 0) {
		close(pasv_fd);
		pasv_fd = -1;
	}
	if (pasv_con_fd > 0) {
		close(pasv_con_fd);
		pasv_con_fd = -1;
	}
	if (port_con_fd > 0) {
		close(port_con_fd);	
		port_con_fd = -1;
	}
	return 0;
}

// handle signal
void sig_child(int signo)
{
	pid_t pid;
	int stat;

	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) 
		printf("child %d terminated\n", pid);
}

int get_con_fd()
{
	//struct sockaddr_in cliaddr;
	if (pasv_fd > 0) {
		if ((pasv_con_fd = accept(pasv_fd, NULL, NULL)) == -1) {
			fprintf(stderr, "accept in get_con_fd:%s\n", strerror(errno));	
			close(pasv_fd);
			pasv_fd = -1;
			pasv_con_fd = -1;
			return (-1);
		}	
		return pasv_con_fd;
	} else if (port_con_fd > 0) { 
		return port_con_fd;	
	}
	return 0;
}
void server_init(void)
{
    int reuse = 1;

    quit = 0;
    pasv_fd = -1;
    pasv_con_fd = -1;

    // timeout control. timeout_type=1, control_timeout; 0, data_timeout;
    timeout_type = 1;
    control_timeout = 100;
    data_timeout = 100;
    default_port = 21;

    if (!isroot()){
        err_quit("Run as root!");	
    }

    if (isdaemon) {
        daemon_init("myftp server", LOG_DAEMON);	
    }

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        fprintf(stderr, "Initialize server: socket error!\n");	
        exit(-1);
    }
    // non-block
    /*int val;*/
    /*if ((val= fcntl(listenfd, F_GETFL, 0)) < 0) {*/
    /*err_quit("fcntl error");	*/
    /*}*/
    /*if (fcntl(listenfd, F_SETFL, val | O_NONBLOCK) < 0) {*/
    /*err_quit("fcntl error");	*/
    /*}*/


    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(default_port);

    if (bind(listenfd , (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        fprintf(stderr, "Initialize server, bind: %s\n", strerror(errno));	
        exit(-1);
    }


    if (signal(SIGCHLD, sig_child) == SIG_ERR) {
        fprintf(stderr, "Initialize server: signal error!\n");
        exit(-1);
    }

    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
        fprintf(stderr, "Initialize server: signal error!\n");
        exit(-1);
    }
}


void useage()
{
	fprintf(stdout, "ftpserver -p port [-d]\n");
}

void parse_cmds(int argc, char **argv)
{
	int opt;
	int err = 0;
	isdaemon = 0;
	while ((opt = getopt(argc, argv, "p:d")) != -1) {
		switch(opt) {
			case 'p':
				default_port = (atoi)(optarg);
				break;
			case 'd':
				isdaemon = 1;	
				break;
			default:
				err = 1;
				break;
		}			

		if (err) {
			useage();
			exit(-1);
		}
	}
}

// handle ftp commands
void handle_cmd(int fd)
{
	char buf[MAXSIZE];
	char cmd[MAXSIZE/2];
	char content[MAXSIZE/2];

	timeout_type = 1;	 // control timeout.
	alarm(control_timeout);
	while (!quit){
		alarm(control_timeout);
		content[0] = '\0';
		if (recv_msg(fd, buf, sizeof(buf)) == -1) {
			fprintf(stderr, "recv_msg error!\n");
			continue;
		}		 
		trimstring(buf, strlen(buf));
		sscanf(buf, "%s %s", cmd, content);	
		for (int i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) {
			if (strcmp(cmd, commands[i].cmd) == 0) {	
				if (commands[i].operation(fd, content) == -1){
					//quit = 1; // error occurs
					// we should do sth. where some commands' operation error
				}
				break;
			}
		}
	}
	fprintf(stdout, "A host gone!\n");
	quit = 0;
}

char *numtomsg(int num)
{
	char buf[8];
	int i;

	sprintf(buf, "%d", num);
	if (strlen(buf) != 3)
		return NULL;
	for (i = 0; i < sizeof(serviceReply)/sizeof(serviceReply[0]); i++) {
		if (strncmp(buf, serviceReply[i], 3) == 0)	
			return serviceReply[i];
	}
	return NULL;
}

// using multi-processes model
void mainloop()
{
	pid_t childpid;
	socklen_t clilen;

	for (; ;) {
		clilen = sizeof(cliaddr);	
		if ((confd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) {
			if (errno == EINTR)	
				continue;
			else {
				fprintf(stderr, "Accept error\n");	
				exit(-1);
			}
		}	

		if ((childpid = fork()) == 0) { // child
			/*int logged = 0;*/
			signal(SIGALRM, handle_alarm);
			pid_t pid = getpid();
			fprintf(stdout, "child pid %d\n", pid);
			close(listenfd);	

			if (send_msg(confd, 220) == -1){
				fprintf(stderr, "sendmsg error!\n");
				exit(-1);	
			}
      /*
			 *if (recv_msg(confd, buf, sizeof(buf)) == -1) {
			 *  fprintf(stderr, "recv_msg error!\n");	
			 *  exit(-1);
			 *}
			 *trimstring(buf, strlen(buf));
			 *for (int i = 0; i < sizeof(users)/sizeof(users[0]); i++) {
			 *  if (!strcmp(buf, users[i].username)){
			 *    send_msg(confd, 331);	
			 *    recv_msg(confd, buf, sizeof(buf));
			 *    trimstring(buf, strlen(buf));
			 *    if (!strcmp(buf, users[i].passwd)) {
			 *      send_msg(confd, 230);
			 *      logged = 1;
			 *      break;
			 *    }
			 *  }
			 *}
			 *if (!logged) {
			 *  fprintf(stderr, "No this uer!\n");
			 *  exit(-1);
			 *}
       */
			
			signal(SIGCHLD, SIG_IGN);

			/*int val;*/
			/*if ((val= fcntl(confd, F_GETFL, 0)) < 0) {*/
				/*err_quit("fcntl error");	*/
			/*}*/
			/*if (fcntl(confd, F_SETFL, val | O_NONBLOCK) < 0) {*/
				/*err_quit("fcntl error");	*/
			/*}*/

			handle_cmd(confd);
			exit(0);
		}
		else if (childpid >0 ) { // father
			fprintf(stdout, "A customer comes\n");	
			signal(SIGCHLD, sig_child);
			close(confd);
			continue;
		}
		else {
			fprintf(stderr, "fork error\n");	
			exit(-1);
		}
	}	
}

int main(int argc, char **argv)
{
	int listenrtn;

	// parse cmds
	parse_cmds(argc, argv);	

	// init server
	server_init();

	// listen
	listenrtn = listen(listenfd, LISTENQ); 
	
	if (listenrtn == -1) {
		fprintf(stderr, "Initialize server: listen error!");	
		exit(-1);
	} 
	if (listenrtn == 0) {
		mainloop();		
	}
	return 0;
}
